/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package visualcbir.board;

import Grid.AbstractBoard;
import Grid.BoardCell;
import Grid.Cell.CellItemInterface;
import Grid.Events.Loader;
import Grid.Events.MoveEvent;
import Grid.Events.MoveListener;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import kmeans.Cluster;
import kmeans.SimpleRunner;

/**
 *
 * @author robertopinho
 */
public class Labeler implements MoveListener {
    
    private Loader loader;
    private AbstractBoard board;
    
    //labels
    private List<Label> labelList;
    private int numLabels;
    private int emptyLabelIndex =-1;
    private int lastLabel=0;
    
    
    
    public Labeler(Loader l, int k){
        this.labelList = new ArrayList<Label>();
        this.loader = l;
        
        this.numLabels = k;
        emptyLabelIndex =0; // Points to next empty label
        
        
    }

    public void resetLabels() {
        this.labelList.clear();
        createNewLabels();
        initLabelsKmeans();
    }
    
    
    private void createNewLabels(){
        Label label;
        while (this.labelList.size() < this.getNumLabels()) {
            label = this.loader.newLabel();
            label.setId(this.labelList.size()+lastLabel);
            this.labelList.add(label);
            label.setLabeler(this);
            
                    
        }
        lastLabel += this.labelList.size();

    }
    
    public void moveReceived(MoveEvent event) {
        
        CellItemInterface movedItem = event.movedItem();
        BoardCell itemCell = board.getCell(movedItem.getRow(), movedItem.getCol());
        Label cellLabel = itemCell.getAssignedLabel();
        Label itemLabel = movedItem.getAssignedLabel();
    
        
        createNewLabels();
        
        
        if (itemLabel != null) {
            
            if (cellLabel == null) {
                itemCell.setAssignedLabel(itemLabel);
                return;
            }
            
            if(cellLabel==itemLabel){
                return;
            }
            
            //if cell label conflict, chooses the best fit
            double itemFit = itemLabel.fitDistance(movedItem);
            double cellFit = cellLabel.fitDistance(movedItem);
            if(itemFit<cellFit){
                itemCell.setAssignedLabel(itemLabel);
            } else {
                movedItem.setAssignedLabel(cellLabel);
            }
                    
            
            
        } else {

        
            if (emptyLabelIndex != -1) {  // Beginning of the map, labels still empty
                movedItem.setAssignedLabel(this.labelList.get(emptyLabelIndex));
                itemCell.setAssignedLabel(this.labelList.get(emptyLabelIndex));
                emptyLabelIndex++;
                if (emptyLabelIndex >= this.getNumLabels()) {
                    emptyLabelIndex = -1;
                }
                return;
            }

        
            //if (cellLabel == null) { // both null, look for match
                Set<Label> possibleLabelSet = new HashSet<Label>();
                for(CellItemInterface neighbor : 
                    board.getNeighbors(movedItem.getRow(), movedItem.getCol())){
                        possibleLabelSet.add(neighbor.getAssignedLabel());
                    
                }
                double minFitDistance = Double.POSITIVE_INFINITY;
                Label bestLabel = null;
                for(Label possibleLabel : possibleLabelSet){
                    double fitDistance = possibleLabel.fitDistance(movedItem);
                    if(fitDistance<minFitDistance){
                        minFitDistance = fitDistance;
                        bestLabel = possibleLabel;
                    }
                    movedItem.setAssignedLabel(bestLabel);
                    itemCell.setAssignedLabel(bestLabel);
                }
                
            //} else { // Assign cell label to item
            //    movedItem.setAssignedLabel(cellLabel);
            //}

        }
        
    }
        
    public void bestMatchAlgorithm(MoveEvent event){    
        
        
        List<Label> matchList = new ArrayList<Label>();
        
        for(Label l:  this.labelList){
            if(l.match(event.movedItem())){
                matchList.add(l);
            }
        }
        
        switch (matchList.size()) {
            case 0:
                labelList.add(new ClumpDocLabel());
                labelList.get(labelList.size() - 1).addItem(event.movedItem());
                break;
            case 1:
                matchList.get(0).addItem(event.movedItem());
                break;
            default:
                matchList.get(0).addItem(event.movedItem());
                join(matchList);
        }
        
        for(Label l:  this.labelList){
            System.err.print(l.getItemCount()+"\t");
            System.err.println(l.getId());
        }
        System.err.println("**************");
        
    }

    public void setBoard(AbstractBoard b) {
        this.board = b;
        checkLabelInit();
    }

    public List<Label> getLabelList() {
        return labelList;
    }

    public void setLabelList(List<Label> labelList) {
        this.labelList = labelList;
    }

    private void checkLabelInit() {
        if(emptyLabelIndex!=-1 &&                    // if empty labels and 
                board.itemCount()>this.getNumLabels()){   // board has enough cells
            initLabels();
        }
    }

    
    
    private void initLabels() {
        createNewLabels();
        initLabelsKmeans();
    }
    
    private void initLabelsKmeans(){
        Integer numClusters = this.getNumLabels();
        
        HashMap<Integer,CellItemInterface> pointItem 
                =  new HashMap<Integer,CellItemInterface>();
        
        double[][] points;
        LinkedHashSet<CellItemInterface> items = board.getItems();
        
        points = new double[items.size()][];

        Iterator<CellItemInterface> it = items.iterator();
        for(int i =0;i<items.size();i++){
            points[i]=new double[2];
            CellItemInterface item = it.next();
            points[i]=item.getVector();
            pointItem.put(new Integer(i), item);
            
        }
        
        
        Cluster[] clusters = new SimpleRunner().run(points, numClusters);
        
        for(int j=0;j<clusters.length;j++){
            for(int ind :clusters[j].getMemberIndexes()){
                CellItemInterface item = pointItem.get(ind);
                item.setAssignedLabel(this.labelList.get(j));
                board.getCell(item.getRow(), item.getCol())
                        .setAssignedLabel(this.labelList.get(j));
                
            }
        }
        points = null;
        System.gc();
        
    }
    
    private void initLabelsNeighbors(boolean best) {
        List<List<CellItemInterface>> candidateLists = 
                new ArrayList<List<CellItemInterface>>();
        
        
        //Init labels with random elements and list of candidate with neighbors
        int boardSize = board.itemCount();
        LinkedHashSet<CellItemInterface> items = board.getItems();
        emptyLabelIndex =-1; // no need to init labels again
        Label label;
        for(int j=0;j<this.labelList.size();j++){
            label = this.labelList.get(j);
            CellItemInterface initItem = null;
            while(initItem == null){
                int randomIndex = (int)(Math.random()*boardSize);
                Iterator<CellItemInterface> it = items.iterator();
                initItem = it.next();
                for(int i=0;i<randomIndex;i++){
                    initItem = it.next();
                }
                if(initItem.getAssignedLabel()!=null){
                    initItem=null;
                }
            }
            candidateLists.add(new ArrayList<CellItemInterface>());
            initItem.setAssignedLabel(label);
            board.getCell(initItem.getRow(), initItem.getCol())
                        .setAssignedLabel(label);
                
            for(CellItemInterface neighbor: 
                board.getNeighbors(initItem.getRow(), initItem.getCol())){
                    if(neighbor.getAssignedLabel()==null){
                        candidateLists.get(j).add(neighbor);
                    }
                }
            
        }
        
        boolean notEmpty = true;
        
        double[] fit = new double[this.getNumLabels()];
        double[] fitOthers = new double[this.getNumLabels()];
        double fitOther;
        
        while(notEmpty){
            int roundRobin =0;
            int[] robinIndex = new int[this.labelList.size()];
            notEmpty = false;
            //Calculate fit for one row of elements
            for(int j=0;j<this.labelList.size();j++){
                fit[j] = Double.POSITIVE_INFINITY;
                fitOthers[j] = Double.NEGATIVE_INFINITY;
                //select the next element on the queue not assigned yet
                while(candidateLists.get(j).size()>0 && 
                        fit[j] == Double.POSITIVE_INFINITY) {
                    
                    robinIndex[j] = roundRobin%candidateLists.get(j).size();
                    if(candidateLists.get(j).get(robinIndex[j]).getAssignedLabel()!=null){
                      candidateLists.get(j).remove(robinIndex[j]);
                    } else {
                        fit[j] = this.labelList.get(j).fitDistance(
                                candidateLists.get(j).get(robinIndex[j]));
                        fitOthers[j] = Double.POSITIVE_INFINITY;
                        for(int i=0;i<this.labelList.size();i++){
                            if(i==j) continue;
                            fitOther = this.labelList.get(i).fitDistance(
                                candidateLists.get(j).get(robinIndex[j]));
                            if(fitOther<fitOthers[j]){
                                fitOthers[j]=fitOther;
                            }
                            
                        }
                        
                    }
                }
                if(candidateLists.get(j).size()>0){
                    notEmpty = true;
                }
                
            }
            roundRobin++;
            // Select best fit
            // Select lowest fit
            double bestFit = Double.POSITIVE_INFINITY;
            int bestIndex = -1;

            if (best) {
                for (int j = 0; j < this.labelList.size(); j++) {
                    if (fit[j] < bestFit) {
                        bestFit = fit[j];
                        bestIndex = j;
                    }
                }
            } else {
                //select highest to others

                bestFit = Double.NEGATIVE_INFINITY;

                for (int j = 0; j < this.labelList.size(); j++) {
                    if (fitOthers[j] > bestFit) {
                        bestFit = fitOthers[j];
                        bestIndex = j;
                    }
                }
            }

            if(bestIndex!=-1){
                CellItemInterface newItem = 
                        candidateLists.get(bestIndex).get(robinIndex[bestIndex]);
                newItem.setAssignedLabel(this.labelList.get(bestIndex));
                board.getCell(newItem.getRow(), newItem.getCol())
                        .setAssignedLabel(this.labelList.get(bestIndex));
                
                
                candidateLists.get(bestIndex).remove(robinIndex[bestIndex]);
                
                for(CellItemInterface neighbor: 
                board.getNeighbors(newItem.getRow(), newItem.getCol())){
                    if(neighbor.getAssignedLabel()==null){
                        candidateLists.get(bestIndex).add(neighbor);
                    }
                }
            
                
            }
        }
        
        
    }

    
    private void join(List<Label> matchList) {
       int previousSize = matchList.size();
       Set<CellItemInterface> itemSet = new HashSet<CellItemInterface>();
       
       for(Label l: matchList){
           itemSet.addAll(l.getItems());
           this.labelList.remove(l);
       }

       ClumpDocLabel newLabel = new ClumpDocLabel();
       newLabel.addItems(itemSet);
       
       
       
    }

    public int getNumLabels() {
        return numLabels;
    }

    public void setNumLabels(int numLabels) {
        this.numLabels = numLabels;
    }

}
